package com.zly.distributed.lock.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zly.distributed.lock.lock.DescributedLockClient;
import com.zly.distributed.lock.lock.DescributedRedisLock;
import com.zly.distributed.lock.mapper.StockMapper;
import com.zly.distributed.lock.pojo.Stock;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j
//@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class StockService {
    //   private Stock stock= new Stock();
    private ReentrantLock lock = new ReentrantLock();
    @Resource
    private StockMapper stockMapper;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

//  @Autowired
//    private DescributedRedisLock descributedRedisLock;
    @Resource
    private DescributedLockClient descributedLockClient;

//      hash + lua 实现可重入锁
//      if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1
//      then
//      redis.call('hincrby',KEYS[1],ARGV[1],1)
//      redis.call('expire',KEYS[1],ARGV[2])
//      return 1
//      else return 0
//      end
//      <p>
//      * if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then redis.call('hincrby',KEYS[1],ARGV[1],1)  redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end
//      key: lock
//      argv:
//      <p>
//      if redis.call('hexists','lock',uuid)==0
//      then
//      return nil
//      elseif redis.call('hincrby','lock',uuid,-1) == 0
//      then
//      reurn redis.call('del',KEYS[1])
//      else
//      return 0
//      end

    public void deduct() {
        DescributedRedisLock redisLock = this.descributedLockClient.getRedisLock("lock");
        redisLock.lock();
        try {
            //        查询库存信息
            String stock = stringRedisTemplate.opsForValue().get("stock").toString();
            if (stock != null && !stock.isEmpty()) {
                Integer count = Integer.valueOf(stock);
                if (count > 0) {
                    stringRedisTemplate.opsForValue().set("stock", String.valueOf(--count));
                }
            }
        } finally {
            redisLock.unlock();
        }


    }

    public void deduct6() {
        //if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del','KEYS[1]) else return 0 end 1 lock lock
//        if redis.call('get',KEYS[1])==ARGV[1]
//        then
//          return redis.call('del','KEYS[1])
//        else
//        return 0
//        end
//
        String uuid = UUID.randomUUID().toString();
        // 加锁操作 设置过期时间防止意外宕机锁无法释放
        Boolean lock1 = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        while (Boolean.FALSE.equals(lock1)) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        try {
//            this.stringRedisTemplate.expire("stock",3, TimeUnit.SECONDS);
            String stock = stringRedisTemplate.opsForValue().get("stock");
            if (stock != null && !stock.isEmpty()) {
                Integer count = Integer.valueOf(stock);
                if (count > 0) {
                    stringRedisTemplate.opsForValue().set("stock", String.valueOf(--count));
                }
            }
        } finally {
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                    "then " +
                    "return redis.call('del',KEYS[1]) " +
                    "else return 0 " +
                    "end ";
            //先判断再解锁 防误删
            this.stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock", uuid));
//            if (StringUtils.equals(stringRedisTemplate.opsForValue().get("lock"), uuid)) {
//                // 解锁
//                stringRedisTemplate.delete("lock");
//            }
        }

    }

    /**
     * redis 事务
     */
    public void deduct5() {
        this.stringRedisTemplate.execute(new SessionCallback<Object>() {


            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                //        watch
                operations.watch("stock");
//        查询库存信息并锁定
                String stock = (String) operations.opsForValue().get("stock");
//        判断库存
                if (stock != null && !stock.isEmpty()) {
                    Integer count = Integer.valueOf(stock);
                    if (count > 0) {
//                multi
                        operations.multi();
                        operations.opsForValue().set("stock", String.valueOf(--count));
//                exec 执行事务
                        List exec = operations.exec();
                        if (exec == null && exec.isEmpty()) {
                            try {
                                Thread.sleep(40);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                            deduct();
                        }
//                        log.info("Stock in redis is decreased: {}", count);
                        return exec;
                    }
                }
                return null;
            }
        });

    }

    /**
     * for update 行级锁
     */
    //    @Transactional
    public void deduct4() {
//        查询库存信息并锁定
        List<Stock> stocks = this.stockMapper.selectList(new LambdaQueryWrapper<Stock>().eq(Stock::getProductCode, "10010"));
//        这里示例选择第一个
        Stock stock = stocks.get(0);
//        判断库存
        if (stock != null && stock.getCount() >= 0) {
            Integer version = stock.getVersion();
            stock.setVersion(stock.getVersion() + 1);
            stock.setCount(stock.getCount() - 1);
            if (this.stockMapper.update(stock, new LambdaQueryWrapper<Stock>().eq(Stock::getId, stock.getId()).eq(Stock::getVersion, version)) == 0) {
//                如果更新失败则重试
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                deduct4();
            }
            log.info("Stock库存还有:{}", stock.getCount());
        }
    }

    /**
     * 事务
     */
    @Transactional
    public void deduct3() {
//        查询库存信息并锁定
        List<Stock> stocks = this.stockMapper.queryStock("10010");
//        这里示例选择第一个
        Stock stock = stocks.get(0);
//        判断库存
        if (stock != null && stock.getCount() >= 0) {
            stock.setCount(stock.getCount() - 1);
            stockMapper.updateById(stock);
            log.info("Stock库存还有:{}", stock.getCount());
        }
    }
    /**
     *  不同的隔离级别
     */
    //    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    @Transactional
    public void deduct2() {
//       stock.setStock(stock.getStock()-1);
//       lock.lock();
        try {
//           update insert delete 写操作会加锁
//         sql:  update db_stock set count =count -1 where product_code = '10010' and count >= 1
//           Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>().eq(Stock::getProductCode, "10010"));
//           if (stock!=null&& stock.getCount()>0) {
//               stock.setCount(stock.getCount()-1);
//               stockMapper.updateById(stock);
//               log.info("Stock库存还有:{}",stock.getCount());
//           }
            int i = stockMapper.updateStock("10010", 1);
            System.out.println("i = " + i);
        } finally {
//           lock.unlock();
        }
    }
}
